package com.gopay.common.security.validcode.manager.impl;

import java.lang.reflect.UndeclaredThrowableException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import javax.annotation.Resource;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import com.gopay.common.cipher.utils.SensitiveInfoUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import com.gopay.common.constants.validcode.CodeType;
import com.gopay.common.domain.vs.VsValidCodeDtl;
import com.gopay.common.security.validcode.dao.ValidCodeDAO;
import com.gopay.common.util.CommonUtils;
import com.gopay.common.util.DateUtils;

@Service("payValidCodeManager")
public class PayValidCodeManagerImpl extends AbstractValidCode {

    @Resource(name = "validCodeDAO")
    private ValidCodeDAO validCodeDAO;

    // 过期时间(秒)
    private final static int EXPIR = 1800;

    @Override
    protected VsValidCodeDtl generateCode(CodeType type, String voID) {
        VsValidCodeDtl code = new VsValidCodeDtl();

        // 生成随机码
        String uid = createNumberCode(6);

        // 当前时间
        Date curDate = DateUtils.getSystemDate();

        expirTime = EXPIR;

        code.setCode(uid);
        code.setType(type.value);
        code.setVoID(voID);
        code.setExpir(expirTime);
        code.setCreated(curDate);
        code.setModified(curDate);

        return code;
    }

    @Override
    public boolean valid(CodeType type, String voID, String code, String timeOut) {
        boolean flag = false;
        List<VsValidCodeDtl> list = validCodeDAO.getValidCodeAll(type, voID);
        if (list == null || list.size() == 0) {
            return false;
        }

        // 当前时间
        Date currentDate = DateUtils.getSystemDate();
        long curDate = currentDate.getTime();
        VsValidCodeDtl validCode = null;

        for (Iterator<VsValidCodeDtl> it = list.iterator(); it.hasNext();) {
            validCode = it.next();

            long dbDate = validCode.getCreated().getTime();
            long curr = (curDate - dbDate);
            long expir = expirTime * 1000;

            if (StringUtils.isNotBlank(timeOut)) {
                expir = Long.parseLong(timeOut) * 1000;
            }
            // 验证码是否过期 AND 验证码是否匹配
            if (curr <= expir && StringUtils.equals(code, SensitiveInfoUtils.smartDecryptWithCheck(validCode.getCode()))) {
                flag = true;
                break;
            }
        }
        // 批量更新修改时间
        updateModified(list);
        return flag;
    }

    /**
     * 批量更新修改时间
     * 
     * @param list
     */
    private void updateModified(List<VsValidCodeDtl> list) {
        if (list == null || list.size() == 0) {
            return;
        }

        // 当前时间
        Date currentDate = DateUtils.getSystemDate();

        VsValidCodeDtl validCode = null;
        for (Iterator<VsValidCodeDtl> it = list.iterator(); it.hasNext();) {
            validCode = it.next();

            // 更新修改时间，以证明验证过
            validCode.setModified(currentDate);
            validCodeDAO.update(validCode);
        }
    }

    public static String createNumberCode(int length) {
        String seed = "12345678901234567890";
        String steps = CommonUtils.formatDate(new Date(), "yyyyMMddHHmmssS");
        String s = generateTOTP(seed, steps, "" + length);
        return s;
    }

    public static String generateTOTP(String key, String time, String returnDigits) {
        return generateTOTP(key, time, returnDigits, "HmacSHA1");
    }

    public static String generateTOTP(String key, String time, String returnDigits, String crypto) {
        int codeDigits = Integer.decode(returnDigits).intValue();
        String result = null;
        while (time.length() < 16) {
            time = "0" + time;
        }
        byte[] msg = hexStr2Bytes(time);
        byte[] k = hexStr2Bytes(key);

        byte[] hash = hmac_sha(crypto, k, msg);
        int offset = hash[hash.length - 1] & 0xf;
        int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16)
                | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
        int otp = binary % DIGITS_POWER[codeDigits];
        result = Integer.toString(otp);
        while (result.length() < codeDigits) {
            result = "0" + result;
        }
        return result;
    }

    /**
     * This method converts a HEX string to Byte[]
     * 
     * @param hex
     *            : the HEX string
     * 
     * @return: a byte array
     */
    private static byte[] hexStr2Bytes(String hex) {
        // Adding one byte to get the right conversion
        // Values starting with "0" can be converted
        byte[] bArray = new BigInteger("10" + hex, 16).toByteArray();
        // Copy all the REAL bytes, not the "first"
        byte[] ret = new byte[bArray.length - 1];
        for (int i = 0; i < ret.length; i++) {
            ret[i] = bArray[i + 1];
        }
        return ret;
    }

    private static byte[] hmac_sha(String crypto, byte[] keyBytes, byte[] text) {
        try {
            Mac hmac;
            hmac = Mac.getInstance(crypto);
            SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
            hmac.init(macKey);
            return hmac.doFinal(text);
        } catch (GeneralSecurityException gse) {
            throw new UndeclaredThrowableException(gse);
        }
    }

    private static final int[] DIGITS_POWER
    // 0 1 2 3 4 5 6 7 8
    = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };

    public ValidCodeDAO getValidCodeDAO() {
        return validCodeDAO;
    }

    public void setValidCodeDAO(ValidCodeDAO validCodeDAO) {
        this.validCodeDAO = validCodeDAO;
    }

    public static int getExpir() {
        return EXPIR;
    }

    public static int[] getDigitsPower() {
        return DIGITS_POWER;
    }

}
